package org.smart4j.framework.helper;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.annotation.Aspect;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.proxy.AspectProxy;
import org.smart4j.framework.proxy.Proxy;
import org.smart4j.framework.proxy.ProxyManager;
import org.smart4j.framework.proxy.TransactionProxy;

import java.lang.annotation.Annotation;
import java.util.*;

/*
* 方法拦截助手类
* */
public final class AopHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);

    static {
        try {
            Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
            Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
            //遍历目标类代理map
            for (Map.Entry<Class<?>, List<Proxy>> targetEntry : targetMap.entrySet()){
                //获取目标类和 它的代理链，并生成真正的代理对象，然后用代理对象替换BeanMap中目标类的实例对象
                Class<?> targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                Object proxy = ProxyManager.createProxy(targetClass,proxyList);
                BeanHelper.setBean(targetClass, proxy); //替换
            }

        }catch (Exception e){
            LOGGER.error("aop failure", e);
        }
    }

    //获取所有要被进行代理的 目标类集合
    private static final Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception {
        Set<Class<?>> targetClassSet = new HashSet<Class<?>>();
        Class<? extends Annotation> annotation = aspect.value();
        if (annotation != null && !annotation.equals(Aspect.class)) {
            targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
        }
        return targetClassSet;
    }

    /*获取代理类 及其 目标类集合 之间的映射关系，一个代理类可以对应一个或多个目标类
    *(1)普通切面代理，需要扩展AspectProxy抽象类，还需要带有Aspect注解。
    *(2)事务代理类：TransactionProxy类。
     */
    private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
        //proxyMap存放代理类和目标类集合
        Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();
        addAspectProxy(proxyMap);
        addTransactionProxy(proxyMap);
        return proxyMap;
    }

    //获取普通切面代理与目标类集合 之间的映射关系
    private static void addAspectProxy(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception{
        //获取代理类集合
        Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
        //遍历代理类集合
        for (Class<?> proxyClass : proxyClassSet) {
            //如果代理类上有Aspect注解，则进行处理
            if (proxyClass.isAnnotationPresent(Aspect.class)) {
                Aspect aspect = proxyClass.getAnnotation(Aspect.class);
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                proxyMap.put(proxyClass, targetClassSet);
            }
        }
    }

    //获取事务代理类（TransactionProxy类）与目标类集合之间的映射关系
    public static void addTransactionProxy(Map<Class<?>, Set<Class<?>>> proxyMap){
        Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
        proxyMap.put(TransactionProxy.class, serviceClassSet);
    }

    //获取 目标类 与 代理对象列表 之间的映射关系
    private static  Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception{
        Map<Class<?>, List<Proxy>> targetMap = new HashMap<Class<?>, List<Proxy>>();
        //遍历代理类及其目标类集合的Map：   proxyMap
        for (Map.Entry<Class<?>, Set<Class<?>>>  proxyEntry : proxyMap.entrySet()){
            //得到代理类
            Class<?> proxyClass = proxyEntry.getKey();
            //得到目标类集合
            Set<Class<?>> targetClassSet = proxyEntry.getValue();
            //遍历目标类集合
            for (Class<?> targetClass : targetClassSet){
                //实例化一个 代理类。 代理类需要继承了AspectProxy抽象类，还带有Aspect注解。
                Proxy proxy = (Proxy)proxyClass.newInstance();
                //如果map中已经有目标类，那么就往目标类的代理对象列表中加入这个代理对象。
                //如果没有该目标类，则新为它创建一个 代理对象链表，并把代理对象加入其中,最后加入map中
                if (targetMap.containsKey(targetClass)){
                    targetMap.get(targetClass).add(proxy);
                } else {
                    List<Proxy> proxyList = new ArrayList<Proxy>();
                    proxyList.add(proxy);
                    targetMap.put(targetClass, proxyList);
                }
            }
        }
        return targetMap;
    }

}
